﻿
#include "mesh.h"


#pragma warning(push)
#pragma warning( disable : 4290 )


namespace d3dx9_dynamic_load
{


	Mesh::~Mesh()
	{
		if(m_pAdjacency)
		{
			m_pAdjacency->Release();
			m_pAdjacency = NULL;
			{
				ID3DXBuffer** pp = const_cast<ID3DXBuffer**> (&m_pAdjacency);
				*pp = NULL;
			}
		}

		if(m_pMesh)
		{
			m_pMesh->Release();
			{
				ID3DXMesh** pp = const_cast<ID3DXMesh**>(&m_pMesh);
				*pp = NULL;
			}
		}
	}

	Mesh::Mesh(IDirect3DDevice9* const device, 
		CD3DX9_Dll_Loader* const loader,
		const WCHAR* filename,
		const meshCreateOptions& opt) throw(HRESULT) : 
						m_pMesh(NULL), 
						m_numSubsets(0), 
						m_pd3dxloader(NULL),
						m_BoundBoxMinCoord( D3DXVECTOR3(0.0f,0.0f,0.0f) ),
						m_BoundBoxMaxCoord( D3DXVECTOR3(0.0f,0.0f,0.0f) )
	{

		HRESULT hr=0;
 
		assert(loader);
		assert(device);


		ID3DXMesh* temp = NULL;
		DWORD numsubsets =0;
	
		hr |= loader->D3DXLoadMeshFromXW(filename, opt.Options,device,
			&m_pAdjacency, NULL, NULL, &numsubsets, &temp);

		//__in   LPCTSTR pFilename,
		//	__in   DWORD Options,
		//	__in   LPDIRECT3DDEVICE9 pD3DDevice,
		//	__out  LPD3DXBUFFER *ppAdjacency,
		//	__out  LPD3DXBUFFER *ppMaterials,
		//	__out  LPD3DXBUFFER *ppEffectInstances,
		//	__out  DWORD *pNumMaterials,
		//	__out  LPD3DXMESH *ppMesh


		if FAILED(hr)
		{
			assert(  false  && "  failed operation ");
			throw(hr);
		}

		{
			ID3DXMesh** pp = const_cast<ID3DXMesh**>(&m_pMesh);
			*pp = temp;
		}

		{
			DWORD* ptemp = const_cast<DWORD*>( &m_numSubsets );
			*ptemp = numsubsets;
		}

		{
			CD3DX9_Dll_Loader** pp = const_cast<CD3DX9_Dll_Loader**>(&m_pd3dxloader);
			*pp =  loader;
		}

		// compute bound

		{
			D3DXVECTOR3  boundBoxMinCoord = D3DXVECTOR3( 0.0f , 0.0f , 0.0f );
			D3DXVECTOR3  boundBoxMaxCoord = D3DXVECTOR3( 0.0f , 0.0f , 0.0f );

			D3DXVECTOR3  sphereCenter = D3DXVECTOR3( 0.0f , 0.0f , 0.0f );
			FLOAT        sphereRadius = 0.0f;

			hr |= ComputeBounds(device, loader, m_pMesh, 
				&boundBoxMinCoord, &boundBoxMaxCoord, 
				&sphereCenter, &sphereRadius);

			if FAILED(hr)
			{
				assert(  false  && "  failed operation ");
				throw(hr);
			}

			{
				D3DXVECTOR3* temp = const_cast<D3DXVECTOR3*>(&m_BoundBoxMinCoord);
				*temp = boundBoxMinCoord;

				temp = const_cast<D3DXVECTOR3*>(&m_BoundBoxMaxCoord);
				*temp = boundBoxMaxCoord;

				temp = const_cast<D3DXVECTOR3*>( &m_BoundSphereCenter );
				*temp = sphereCenter;

				FLOAT* ftemp = const_cast<FLOAT*> ( &m_BoundSphereRadius );
				*ftemp = sphereRadius;

			}


		}


		assert(hr==0);

	}




	//================================================================

   HRESULT RebuildMesh( ID3DXMesh** ppInOut, 
						IDirect3DDevice9* const device,
					    CD3DX9_Dll_Loader* const loader)
   {
	   HRESULT hr=0;

	   if(ppInOut == NULL)
	   {
		   assert(   false && " bad params ");
		   return  E_FAIL;

	   }

	   if(*ppInOut == NULL)
	   {
		   assert(   false && " bad params ");
		   return  E_FAIL;

	   }

	   if( (!device) || (!loader) )
	   {

		   assert(  false && " bad params " );
		   return E_FAIL;
	   }




	   DWORD opt = D3DXMESH_MANAGED;

	   ID3DXMesh* pMesh =  *ppInOut; 

	   // Create a new vertex declaration to hold all the required data
	   const D3DVERTEXELEMENT9 vertexDecl[] =
	   {
		   { 0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
		   { 0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
		   { 0, 20, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0 },
		   { 0, 32, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,  0 },
		   { 0, 44, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0 },
		   D3DDECL_END()
	   };

	   LPD3DXMESH pTempMesh = NULL;

	   // Clone mesh to match the specified declaration: 
	   if ( FAILED( pMesh->CloneMesh( pMesh->GetOptions(), vertexDecl,
		   device, &pTempMesh ) ))
	   {

		   if( pTempMesh )
		   {
			   pTempMesh->Release();
			   pTempMesh = NULL;
		   }

		   assert(  false && " operation failed ");
		   return E_FAIL;
	   }

	   //====================================================================//
	   // Check if the old declaration contains normals, tangents, binormals //
	   //====================================================================//
	   bool bHadNormal   = false;
	   bool bHadTangent  = false;
	   bool bHadBinormal = false; 

	   D3DVERTEXELEMENT9 vertexOldDecl[ MAX_FVF_DECL_SIZE ];

	   if ( pMesh && SUCCEEDED( pMesh->GetDeclaration( vertexOldDecl ) ) )
	   {
		   // Go through the declaration and look for the right channels,
		   // hoping for a match:
		   for( UINT iChannelIndex = 0; 
			   iChannelIndex < loader->D3DXGetDeclLength( vertexOldDecl ); 
			   iChannelIndex++ )
		   {
			   if( vertexOldDecl[iChannelIndex].Usage == D3DDECLUSAGE_NORMAL )
			   {
				   bHadNormal = true;
			   }

			   if( vertexOldDecl[iChannelIndex].Usage == D3DDECLUSAGE_TANGENT )
			   {
				   bHadTangent = true;
			   }

			   if( vertexOldDecl[iChannelIndex].Usage == D3DDECLUSAGE_BINORMAL )
			   {
				   bHadBinormal = true;
			   }
		   }  
	   } 

	   if ( pTempMesh == NULL && ( bHadNormal == false || bHadTangent == false || bHadBinormal == false ))
	   {
		   // We failed to clone the mesh and we need the tangent 
		   // space for our effect:
		   return E_FAIL;
	   }

	   //==============================================================//
	   // Generate normals / tangents / binormals if they were missing //
	   //==============================================================//
	   if( pMesh )
	   {
		   pMesh ->Release();
		   pMesh = NULL;
	   }

	   pMesh = pTempMesh;

	   if( ! bHadNormal )
	   {
		   // Compute normals in case the meshes have them
		   loader->D3DXComputeNormals( pMesh, NULL );
	   }  

	   DWORD *rgdwAdjacency = NULL;
	   rgdwAdjacency = new DWORD[ pMesh->GetNumFaces() * 3 ];

	   if( rgdwAdjacency == NULL )
	   {
		   return E_OUTOFMEMORY;
	   }

	   hr |= pMesh->GenerateAdjacency( 1e-6f, rgdwAdjacency );

	   if FAILED(hr) 
	   { 
		   assert(  false && " operation error Error ");
		   return hr;
	   }

	   // Optimize the mesh for this graphics card's vertex cache 
	   // so when rendering the mesh's triangle list the vertices will 
	   // cache hit more often so it won't have to re-execute the vertex shader 
	   // on those vertices so it will improve perf. 

	   hr |=  pMesh->OptimizeInplace( D3DXMESHOPT_VERTEXCACHE,
		   rgdwAdjacency, NULL, NULL, NULL);
	   if FAILED(hr) { throw std::runtime_error("Error"); }

	   if ( ! bHadTangent || ! bHadBinormal )
	   {
		   ID3DXMesh* pNewMesh;

		   // Compute tangents, which are required for normal mapping
		   if ( FAILED( loader->D3DXComputeTangentFrameEx( 
			   pMesh,
			   D3DDECLUSAGE_TEXCOORD, 0, D3DDECLUSAGE_TANGENT, 0,
			   D3DDECLUSAGE_BINORMAL, 0, 
			   D3DDECLUSAGE_NORMAL, 0, 0, rgdwAdjacency, -1.01f,
			   -0.01f, -1.01f, &pNewMesh, NULL ) ) )
		   {
			   return E_FAIL;
		   }

		   if( pMesh )
		   {
			   pMesh ->Release();
			   pMesh = NULL;
		   }


		   pMesh = pNewMesh;
	   }  


	   if(rgdwAdjacency)
	   {
		   delete [] rgdwAdjacency;
		   rgdwAdjacency = NULL;
	   }

	   *ppInOut = pMesh;
 
	   return hr;
   }

   //=================================================================

   HRESULT ComputeBounds(
					IDirect3DDevice9* const device,
					CD3DX9_Dll_Loader* const  loader, 
					ID3DXMesh* const mesh,

					D3DXVECTOR3* box_min,  
					D3DXVECTOR3* box_max,

					D3DXVECTOR3* sphere_center,
					FLOAT*       sphere_radius
				 )
   {

	   HRESULT hr =0;

	   void* ptr = NULL;
	   hr |= mesh->LockVertexBuffer(0,  &ptr );

	   if FAILED(hr)
	   {
		   assert(  false  &&  "failed operation");
		   return hr;
	   }

	   hr |=  loader->D3DXComputeBoundingBox(
		   (D3DXVECTOR3*)ptr,
		   mesh->GetNumVertices(),   //DWORD NumVertices,
		   mesh->GetNumBytesPerVertex(), // DWORD dwStride,
		   box_min, //* pMin,
		   box_max // * pMax
		   );

	   hr |= loader->D3DXComputeBoundingSphere(
		   (D3DXVECTOR3*)ptr, //CONST D3DXVECTOR3 * pFirstPosition,
		   mesh->GetNumVertices(), //DWORD NumVertices,
		   mesh->GetNumBytesPerVertex(), //DWORD dwStride,
		   sphere_center      , // * pCenter,
		   sphere_radius   //pRadius
		   );



	   hr |= mesh->UnlockVertexBuffer();

	   if FAILED(hr)
	   {
		   assert(  false  &&  "failed operation");
		   return hr;
	   }



	   assert(hr==0);
	   return hr;
   }




}


//==============================================================

#ifdef __USE_TEST_D3DX9LOADER_MESH_CLASS__

#include <d3d9.h>
#pragma comment (lib,"d3d9.lib")

#define TEST_APPL_WIND_TITLE  L"TestApplication"


namespace d3dx9_dynamic_load
{

	namespace test 
	{
 
		//---------------------------------------------------------------- 
		// Global variables
		//---------------------------------------------------------------- 
		LPDIRECT3D9         g_pD3D = NULL; // Used to create the D3DDevice
		LPDIRECT3DDEVICE9   g_pd3dDevice = NULL; // Our rendering device
		CD3DX9_Dll_Loader* g_loader = NULL;
		LPDIRECT3DTEXTURE9 g_pMeshTexture = NULL;
		Mesh*  g_pmesh = NULL;

		// g_mousexPos g_mouseyPos
		static float g_mousexPos = 0.0f;  
		static float g_mouseyPos = 0.0f;


		static HRESULT OnCreateDevice(HWND hWnd, IDirect3DDevice9* device )
		{
			HRESULT hr=0;





			return hr;
		}

		static void OnDestroyDevice()
		{
 
 
			if(g_pMeshTexture)
			{
				g_pMeshTexture->Release();
				g_pMeshTexture = NULL;
			}

			if(g_pmesh)
			{
				delete g_pmesh;
				g_pmesh = NULL;
			}


		}


		static HRESULT RecreateDevice(HWND hWnd )
		{
			HRESULT hr=0;

			if(g_pd3dDevice)
			{
				OnDestroyDevice();
				g_pd3dDevice->Release();
				g_pd3dDevice = NULL;
			}

			WINDOWINFO wi;
			::GetWindowInfo(hWnd, &wi);


			D3DPRESENT_PARAMETERS d3dpp;
			ZeroMemory( &d3dpp, sizeof( d3dpp ) );
			d3dpp.Windowed = TRUE;
			d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
			d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
			d3dpp.BackBufferWidth  = wi.rcClient.right-wi.rcClient.left;
			d3dpp.BackBufferHeight = wi.rcClient.bottom-wi.rcClient.top;

			if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
				hWnd,

				D3DCREATE_HARDWARE_VERTEXPROCESSING, //D3DCREATE_SOFTWARE_VERTEXPROCESSING,

				&d3dpp, &g_pd3dDevice ) ) )
			{
				return E_FAIL;
			}

			hr |= OnCreateDevice(hWnd, g_pd3dDevice);


			assert(hr==0);
			return hr;
		}



		static HRESULT InitD3D( HWND hWnd )
		{
			HRESULT hr=0;

			// Create the D3D object, which is needed to create the D3DDevice.
			if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
				return E_FAIL;

			g_loader = new CD3DX9_Dll_Loader();

			hr |= RecreateDevice(hWnd);

			return S_OK;
		}



		static VOID Cleanup()
		{
			if( g_pd3dDevice != NULL )
			{
				OnDestroyDevice();
				g_pd3dDevice->Release();
				g_pd3dDevice = NULL;
			}

			if( g_pD3D != NULL )
				g_pD3D->Release();


			delete g_loader;
		}




		static VOID Render(IDirect3DDevice9* device)
		{
			HRESULT hr =0;
			static int count =0;

			if( NULL == g_pd3dDevice )
				return;

			// Clear the back buffer to a blue color
			hr |= device->Clear( 0, 
					NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 0, 255 ),
				1.0f, 0 );

 

			if(!g_pMeshTexture)
			{
				hr |= g_loader->D3DXCreateTextureFromFileA(device,
					"j:/d3dx9_dynamic_load/d3dx9-dynamic-load/media/mesh.jpg", 
					&g_pMeshTexture	);

				assert( hr==0 && " need a correct texture path " );
			}

			if(g_pmesh==NULL)
			{
				meshCreateOptions opt;
				g_pmesh = new Mesh(device, g_loader,
					L"j:/d3dx9_dynamic_load/d3dx9-dynamic-load/media/mesh.x", 
					opt );
			}


			if(g_pmesh)
			{
				hr |= g_pmesh->DrawSubset(0);
			}


			// Begin the scene
			if( SUCCEEDED( device->BeginScene() ) )
			{
				// Rendering of scene objects can happen here
 

				 

				// render code
 


				// End the scene
				device->EndScene();
			}

			// Present the backbuffer contents to the display
			hr |= device->Present( NULL, NULL, NULL, NULL );


			count++;
			assert(hr==0);

		}




		static LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
		{



			switch( msg )
			{
			case WM_DESTROY:
				Cleanup();
				PostQuitMessage( 0 );
				return 0;

			case WM_PAINT:
				Render(g_pd3dDevice);
				ValidateRect( hWnd, NULL );
				return 0;


			case WM_SIZE:
				RecreateDevice(hWnd);


				break;


			case WM_MOUSEMOVE:	
				g_mousexPos = (float)(  LOWORD(lParam));   // horizontal position 
				g_mouseyPos = (float)(  HIWORD(lParam));   // vertical position 

				break;
			}

			return DefWindowProc( hWnd, msg, wParam, lParam );
		}




		int do_test_mesh()
		{


			using namespace d3dx9_dynamic_load::test;
			//UNREFERENCED_PARAMETER( hInst );
			//			HINSTANCE   hInst;

			// Register the window class
			WNDCLASSEXW wc =
			{
				sizeof( WNDCLASSEX ), CS_CLASSDC, MsgProc, 0L, 0L,
				GetModuleHandle( NULL ), NULL, NULL, NULL, NULL,
				TEST_APPL_WIND_TITLE, NULL
			};

			RegisterClassExW( &wc );

			// Create the application's window
			HWND hWnd = CreateWindowW( TEST_APPL_WIND_TITLE,  TEST_APPL_WIND_TITLE ,
				WS_OVERLAPPEDWINDOW, 100, 100, 800, 600,
				NULL, NULL, wc.hInstance, NULL );

			// Initialize Direct3D
			if( SUCCEEDED( InitD3D( hWnd ) ) )
			{
				// Show the window
				ShowWindow( hWnd, SW_SHOWDEFAULT );
				UpdateWindow( hWnd );

				// Enter the message loop
				MSG msg;
				while( GetMessage( &msg, NULL, 0, 0 ) )
				{
					TranslateMessage( &msg );
					DispatchMessage( &msg );
				}
			}

			UnregisterClassW( TEST_APPL_WIND_TITLE , wc.hInstance );
			return 0;
 

		}
	}


}

INT WINAPI wWinMain( HINSTANCE hInst, HINSTANCE, LPWSTR, INT )
{
	return d3dx9_dynamic_load::test::do_test_mesh();
}


#undef TEST_APPL_WIND_TITLE 

#endif // __USE_TEST_D3DX9LOADER_MESH_CLASS__ 

#pragma warning(pop)


